pub use codespan::{ColumnIndex as Column, LineIndex as Line,ByteIndex as BytePos};
use codespan::ByteOffset;
use ordered_float::NotNan;
use crate::dynf::core::token::{CharLocations};
pub use crate::dynf::core::pos::{Location,Spanned,self};

#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub enum Token<S> {
    Identifier(S),
    Operator(S),
    IntLiteral(i64),
    FloatLiteral(NotNan<f64>),
    CharLiteral(char),
    Let,
    If,
    While,
    Else,
    ElseIf,
    Func,
    Eq,
    LParen, // "("
    RParen, // ")"
    LBrace, // "{"
    LBracket,// "["
    RBracket,// ""]'
    RBrace, // "}"
    Semi,   // ";"
    Comma,  // ","
    Newline,
    Return,
    Break,
    Continue,

    Add, // "+",
    Sub, // "-"
    Mul, //"*"
    Div,  //"/"
    
    Or, //  ||
    And, // &&
    Less, // <
    Greater, // >
    LessEqual, // <=
    GreaterEqual, // >=
    EqEqual, // ==
    NotEqual, // !=
    Not, // !
    True,
    False
}

impl<S> std::fmt::Display for Token<S> where S: std::fmt::Display {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let s = match *self {
            Token::True => "true",
            Token::False => "false",
            Token::Or => "||",
            Token::And => "&&",
            Token::Less => "<",
            Token::Greater => ">",
            Token::LessEqual => "<=",
            Token::GreaterEqual => ">=",
            Token::EqEqual => "==",
            Token::NotEqual => "!=",
            Token::Not => "!",
            Token::Div => "/",
            Token::Mul => "*",
            Token::Add => "+",
            Token::Sub => "-",
            Token::Let => "let",
            Token::If => "if",
            Token::Else => "else",
            Token::ElseIf => "elseif",
            Token::Func => "func",
            Token::Eq => "=",
            Token::LParen => "(",
            Token::RParen => ")",
            Token::LBrace => "{",
            Token::RBrace => "}",
            Token::LBracket => "[",
            Token::RBracket => "]",
            Token::Semi => ";",
            Token::Comma => ",",
            Token::Newline => "Newline",
            Token::While => "while",
            Token::Return => "return",
            Token::Break => "break",
            Token::Continue => "continue",
            Token::Identifier(ref s) => return write!(f, "var({})",s),
            Token::Operator(ref s)   => return write!(f, "`{}`",s),
            Token::IntLiteral(ref s) => return write!(f, "int({})",s),
            Token::FloatLiteral(ref s) => return write!(f, "float({})",s),
            Token::CharLiteral(ref s) => return write!(f, "'{}'",s),
        };
        s.fmt(f)
    }
}


quick_error! {
    #[derive(Clone, Debug, PartialEq, Eq, Hash)]
    pub enum TokenError {
        UnexpectedChar(ch: char) {
            display("unexpected character")
        }
        NonParseableInt {
            display("cannot parse integer, probable overflow")
        }
    }
}
pub type SpError = Spanned<TokenError, Location>;
pub type BorrowedToken<'input> = Token<&'input str>;
pub type SpannedToken<'input>  = Spanned<Token<&'input str>, Location>;

fn error<T>(location: Location, code: TokenError) -> Result<T, SpError> {
    Err(pos::spanned2(location, location, code))
}

pub struct Tokenizer<'input> {
    input: &'input str,
    pub chars: CharLocations<'input>,
    pub start_index: BytePos,
}

impl<'a> Tokenizer<'a> {
    pub fn new(input:&'a str) -> Tokenizer<'a> {
        let chars = CharLocations::new(input);
        Tokenizer {
            input,
            chars,
            start_index: BytePos::from(1)
        }
    }

    fn bump(&mut self) -> Option<(Location,u8)> {
        self.chars.next()
    }

    fn skip_to_end(&mut self) {
        while let Some(_) = self.bump() {}
    }

    fn error<T>(&mut self, location: Location, code: TokenError) -> Result<T, SpError> {
        self.skip_to_end();
        error(location, code)
    }

    fn lookahead(&self) -> Option<(Location, u8)> {
        self.chars.chars.as_str_suffix().first().map(|b| (self.chars.location, b))
    }

    fn test_lookahead<F>(&self, mut test: F) -> bool where F: FnMut(u8) -> bool {
        self.lookahead().map_or(false, |(_, ch)| test(ch))
    }

    fn slice(&self, start: Location, end: Location) -> &'a str {
        let start = start.absolute - ByteOffset::from(self.start_index.to_usize() as i64);
        let end = end.absolute - ByteOffset::from(self.start_index.to_usize() as i64);
        &self.input[start.to_usize()..end.to_usize()]
    }

    fn next_loc(&self) -> Location {
        self.lookahead().as_ref().map_or(self.chars.location, |l| l.0)
    }

    fn take_while<F>(&mut self, start: Location, mut keep_going: F) -> (Location, &'a str)  where F: FnMut(u8) -> bool {
        self.take_until(start, |c| !keep_going(c))
    }

    fn take_until<F>(&mut self, start: Location, mut terminate: F) -> (Location, &'a str) where  F: FnMut(u8) -> bool {
        while let Some((end, ch)) = self.lookahead() {
            if terminate(ch) {
                return (end, self.slice(start, end));
            } else {
                self.bump();
            }
        }
        (self.next_loc(), self.slice(start, self.next_loc()))
    }

    fn identifier(&mut self, start: Location) -> Result<SpannedToken<'a>, SpError> {
        let (end,ident) = self.take_while(start, is_ident_continue);
       
        let token = match ident {
            "else" => Token::Else,
            "elseif" => Token::ElseIf,
            "while" => Token::While,
            "if" => Token::If,
            "let" => Token::Let,
            "func" => Token::Func,
            "true" => Token::True,
            "false" => Token::False,
            "return" => Token::Return,
            "break" => Token::Break,
            "continue" => Token::Continue,
            src => Token::Identifier(src),
        };
        Ok(pos::spanned2(start, end, token))
    }

    fn operator(&mut self, start: Location) -> SpannedToken<'a> {
        let (end, op) = self.take_while(start, is_operator_byte);
        let token = match op {
            "+"  => Token::Add,
            "-"  => Token::Sub,
            "*"  => Token::Mul,
            "/"  => Token::Div,
            "="  => Token::Eq,
            "&&" => Token::And,
            "||" => Token::Or,
            "<"  => Token::Less,
            ">"  => Token::Greater,
            "<=" => Token::LessEqual,
            ">=" => Token::GreaterEqual,
            "==" => Token::EqEqual,
            "!=" => Token::NotEqual,
            "!"  => Token::Not,
            op => Token::Operator(op),
        };
        pos::spanned2(start, end, token)
    }

    fn numeric_literal(&mut self, start: Location) -> Result<SpannedToken<'a>, SpError> {
        let (end, int) = self.take_while(start, is_digit);
        let (start, end, token) = match self.lookahead() {
            Some((_, b'.')) => {
                self.bump();
                let (end, float) = self.take_while(start, is_digit);
                (start,end,Token::FloatLiteral(NotNan::new(float.parse().unwrap()).unwrap()))
            },
            None | Some(_) => {
                if let Ok(val) = int.parse() {
                    (start, end, Token::<&'a str>::IntLiteral(val))
                } else {
                    return self.error(start, TokenError::NonParseableInt);
                }
            }
        };


        Ok(pos::spanned2(start, end, token))
    }
}

fn is_ident_start(ch: u8) -> bool {
    match ch {
        b'_' | b'a'..=b'z' | b'A'..=b'Z' => true,
        _ => false,
    }
}

fn is_ident_continue(ch: u8) -> bool {
    match ch {
        b'0'..=b'9' | b'\'' => true,
        ch => is_ident_start(ch),
    }
}

pub fn is_operator_byte(c: u8) -> bool {
    match c {
        b'!' | b'#' | b'$' | b'%' | b'&' | b'*' | b'+' | b'-' | b'.' | b'/' | b'<' | b'='
        | b'>' | b'?' | b'@' | b'\\' | b'^' | b'|' | b'~' | b':' => true,
        _ => false,
    }
}

fn is_digit(ch: u8) -> bool {
    (ch as char).is_digit(10)
}


impl<'a> Iterator for Tokenizer<'a> {
    type Item = Result<SpannedToken<'a>, SpError>;

    fn next(&mut self) -> Option<Result<SpannedToken<'a>, SpError>> {
        while let Some((start, ch)) = self.bump() {
            return match ch {
                b',' => Some(Ok(pos::spanned2(start, self.next_loc(), Token::Comma))),
                b'(' => Some(Ok(pos::spanned2(start, self.next_loc(), Token::LParen))),
                b')' => Some(Ok(pos::spanned2(start, self.next_loc(), Token::RParen))),
                b'{' => Some(Ok(pos::spanned2(start, self.next_loc(), Token::LBrace))),
                b'}' => Some(Ok(pos::spanned2(start, self.next_loc(), Token::RBrace))),
                b'[' => Some(Ok(pos::spanned2(start, self.next_loc(), Token::LBracket))),
                b']' => Some(Ok(pos::spanned2(start, self.next_loc(), Token::RBracket))),
                b';' => Some(Ok(pos::spanned2(start, self.next_loc(), Token::Semi))),
               //b'\n' => Some(Ok(pos::spanned2(start, self.next_loc(), Token::Newline))),
                ch if is_ident_start(ch) => Some(self.identifier(start)),
                ch if is_digit(ch) || (ch == b'-' && self.test_lookahead(is_digit)) => {
                    Some(self.numeric_literal(start))
                }
                ch if is_operator_byte(ch) => Some(Ok(self.operator(start))),
                ch if (ch as char).is_whitespace() => continue,
                ch => {
                    let ch = self.chars.chars.as_str_suffix().restore_char(&[ch]);
                    Some(self.error(start, TokenError::UnexpectedChar(ch)))
                }
            }
        }
        None
    }
}

#[test]
fn test_let() {
    let code_str = r#"let a = 5 + 3;
                      func f0(num,num1) {
                         return num + num1;
                      }
                      f0(2,3);
                      let d = (f0(a,2) + 1) * 3;
                   "#;
    let mut token_arr = vec![];
    let tokenizer = Tokenizer::new(code_str);
    for e_token in tokenizer {
        match e_token {
            Ok(token) => {
                println!("{}",&token.value);
                token_arr.push(token)
            } ,
            Err(err)  => panic!("err {:?}",err)
        }
    }
}
